﻿using System;
using System.Collections.Generic;
using System.Linq;
using WolvenKit.App.Extensions;
using WolvenKit.App.ViewModels.Shell;
using WolvenKit.Common.Extensions;
using WolvenKit.RED4.Types;

// ReSharper disable InconsistentNaming

namespace WolvenKit.App.ViewModels.Tools.EditorDifficultyLevel;

/// <summary>
/// Helper class to hold the logic for conditional editor field hiding and read-only states.
/// We'll have one instance of this class per difficulty level. 
/// </summary>
public abstract class EditorDifficultyLevelInformation
{
    public abstract EditorDifficultyLevel Level { get; }

    #region field_definitions

    protected static readonly List<string> _informationFields =
    [
        "cookingPlatform",
        "topology",
        "topologyData",
        "topologyDataStride",
        "topologyMetadata",
        "topologyMetadataStride",
        "version",
        "vertexBufferSize"
    ];

    // Properties (by name) that should never be changed by the user
    protected static readonly List<string> _autoGeneratedFieldNames =
    [
        "saveDateTime", "resourceVersion", "cookingPlatform", "renderBuffer", "commonCookData"
    ];

    protected static readonly List<Type> _informationDataTypes =
    [
        typeof(SerializationDeferredDataBuffer),
        typeof(rendRenderTextureBlobMemoryLayout),
        typeof(rendRenderTextureBlobPlacement),
        typeof(rendRenderTextureBlobMipMapInfo),
        typeof(rendRenderTextureBlobTextureInfo),
        typeof(rendRenderTextureBlobSizeInfo),
    ];

    // Fields (by parent class) that should be marked as read-only
    protected static readonly Dictionary<Type, List<string>> _autoGeneratedFields = new()
    {
        { typeof(CMesh), ["geometryHash", "consoleBias"] }, // mesh
        { // mesh
            typeof(rendChunk), [
                "baseRenderMask", "lodMask", "materialId", "mergedRenderMask", "numIndices", "vertexFactory"
            ]
        },
    };

    // Fields (by parent class) that should be marked as read-only
    protected static readonly Dictionary<Type, List<string>> _autoGeneratedInfoFields = new()
    {
        { typeof(CBitmapTexture), ["width", "height"] }, // xbm
        { typeof(rendRenderMeshBlobHeader), ["indexBufferSize", "renderLODs"] }, // mesh
        { typeof(rendChunk), ["numVertices"] } // mesh
    };


    /// <summary>
    /// Some fields should only be hidden if they are not empty (e.g. preloadLocalMaterials in a mesh, or resolvedDependencies in an app).
    /// </summary>
    protected static readonly Dictionary<Type, List<string>> _irrelevantWithDefaultValues = new()
    {
        {
            typeof(CMesh), [
                "preloadExternalMaterials", "preloadLocalMaterials", "preloadLocalMaterialInstances", "inplaceResources",
                "localMaterialInstances"
            ]
        },
        { typeof(inkTextureAtlas), ["activeTexture", "dynamicTexture", "dynamicTextureSlot", "texture"] },
        // .app file: appearance definition: parts override - ArchiveXL will handle this
        { typeof(appearanceAppearancePartOverrides), ["partResource"] },
    };

    /// <summary>
    /// Some fields are irrelevant if they have default values, the user isn't going to set them
    /// </summary>
    protected static readonly Dictionary<Type, List<string>> _lowPrioWithDefaultValues = new()
    {
        // .mesh file: root
        { typeof(CMesh), ["constrainAutoHideDistanceToTerrainHeightMap"] },
        // .app file: root
        { typeof(appearanceAppearanceResource), ["censorshipMapping"] },
        // .app file: appearance definition
        { typeof(appearanceAppearanceDefinition), ["looseDependencies"] },
        { typeof(inkTextureAtlas), ["parts", "slices"] },
        { typeof(rendRenderMeshBlobHeader), ["speedTreeWind"] },
        { typeof(rendChunk), ["baseRenderMask", "mergedRenderMask", "materialId"] },
    };


    /// <summary>
    /// Some fields can be needed if they are not empty (e.g. preloadLocalMaterials).
    /// </summary>
    protected static readonly Dictionary<Type, List<string>> _genericFields = new()
    {
        {
            typeof(CMesh), [
                "boundingBox", "boneNames", "boneVertexEpsilons", "boneRigMatrices", "castGlobalShadowsCachedInCook",
                "castLocalShadowsCachedInCook",
                "castsRayTracedShadowsFromOriginalGeometry", "consoleBias", "floatTrackNames", "forceLoadAllAppearances", "lodBoneMask",
                "isPlayerShadowMesh", "isShadowMesh", "geometryHash",
                "objectType", "resourceVersion", "saveDateTime", "surfaceAreaPerAxis", "useRayTracingShadowLODBias"
            ]
        },
        { // Mesh: Render blob header
            typeof(rendRenderMeshBlobHeader), [
                "bonePositions", "customData", "customDataElemStride", "dataProcessing", "indexBufferOffset", "indexBufferStride",
                "opacityMicromaps", "quantizationOffset", "quantizationScale", "renderChunks"
            ]
        },
        { typeof(inkTextureSlot), ["slices"] },
        // .app file
        {
            typeof(appearanceAppearanceResource), [
                "alternateAppearanceMapping",
                "alternateAppearanceSettingName",
                "alternateAppearanceSuffixes",
                "baseEntity",
                "baseEntityType",
                "baseType",
                "DismEffects",
                "DismWoundConfig",
                "forceCompileProxy",
                "generatePlayerBlockingCollisionForProxy",
                "proxyPolyCount",
                "Wounds",
            ]
        },
        // .app file: appearance definition
        {
            typeof(appearanceAppearanceDefinition), [
                "censorFlags",
                "cookedDataPathOverride",
                "forcedLodDistance",
                "hitRepresentationOverrides",
                "parametersBuffer",
                "parentAppearance",
            ]
        },

        // .app file: appearance definition: parts override
        { typeof(appearancePartComponentOverrides), ["acceptDismemberment"] },

        /*
         * .ent file
         */
        {
            typeof(entEntityTemplate),
            ["backendDataOverrides", "bindingOverrides", "compiledEntityLODFlags", "componentResolveSettings", "includes", "entity"]
        },
        /*
         * .ent/.app file components
         */
        {
            typeof(entGarmentSkinnedMeshComponent), [
                "acceptDismemberment", "autoHideDistance", "isEnabled", "isReplicable", "navigationImpact", "order",
                "overrideMeshNavigationImpact", "renderSceneLayerMask", "visibilityAnimationParam"
            ]
        },
        {
            typeof(entSkinnedMeshComponent), [
                "acceptDismemberment", "autoHideDistance", "isEnabled", "isReplicable", "navigationImpact", "order",
                "overrideMeshNavigationImpact", "renderSceneLayerMask", "visibilityAnimationParam"
            ]
        },
        {
            typeof(entMeshComponent), [
                "autoHideDistance", "isEnabled", "isReplicable", "navigationImpact", "numInstances", "order",
                "overrideMeshNavigationImpact", "objectTypeID", "renderSceneLayerMask", "visibilityAnimationParam"
            ]
        },
        // Right now, these are only used as spacers in app and ent files. Until that changes, we don't really need any properties
        // except for "name".
        {
            typeof(entDebug_MeshComponent), [
                "autoHideDistance", "castLocalShadow", "castRayTracedGlobalShadows", "castRayTracedLocalShadows", "castShadows",
                "castLocalShadows", "chunkMask", "filterName", "forceLODLevel", "forcedLodDistance", "id", "isEnabled",
                "isReplicable", "LODMode", "localTransform", "mesh", "meshAppearance", "motionBlurScale", "navigationImpact",
                "numInstances", "order", "overrideMeshNavigationImpact", "objectTypeID", "order", "overrideMeshNavigationImpact",
                "parentTransform", "renderingScene", "renderSceneLayerMask", "renderingPlane", "visualScale"
            ]
        },
    };

    #endregion

    // Fake read-only
    protected List<string> DisplayAsReadonlyFieldNames { get; } = [];
    protected Dictionary<Type, List<string>> DisplayAsReadonlyFields { get; } = [];

    // Read-only
    protected List<string> ReadonlyFieldNames { get; } = [];
    protected List<Type> ReadonlyDataTypes { get; } = [];
    protected Dictionary<Type, List<string>> ReadonlyFields { get; } = [];


    // Hidden
    protected List<string> HiddenFieldNames { get; } = [];
    protected List<Type> HiddenDataTypes { get; } = [];
    protected Dictionary<Type, List<string>> HideIfDefaultValueFields { get; } = [];
    protected Dictionary<Type, List<string>> HiddenFields { get; } = [];


    // methods
    public virtual bool IsDisplayAsReadonly(string fieldName, Type dataType, ChunkViewModel cvm)
    {
        if (DisplayAsReadonlyFieldNames.Contains(fieldName))
        {
            return true;
        }

        return DisplayAsReadonlyFields.TryGetValue(dataType, out var fields) && fields.Contains(fieldName);
    }

    public virtual bool IsReadonly(string fieldName, Type dataType, ChunkViewModel cvm)
    {
        if (cvm.IsDisplayAsReadOnly)
        {
            return false;
        }

        if (ReadonlyFieldNames.Contains(fieldName) || ReadonlyDataTypes.Contains(dataType))
        {
            return true;
        }

        return ReadonlyFields.TryGetValue(dataType, out var fields) && fields.Contains(fieldName);
    }

    public virtual bool IsHidden(string fieldName, Type dataType, ChunkViewModel cvm)
    {
        if (HiddenFieldNames.Contains(fieldName) || HiddenDataTypes.Contains(dataType) ||
            (HiddenFields.TryGetValue(dataType, out var fields) && fields.Contains(fieldName)))
        {
            return true;
        }

        if (!HideIfDefaultValueFields.TryGetValue(dataType, out var hiddenFields) || !hiddenFields.Contains(fieldName))
        {
            return false;
        }

        return
            // empty array
            cvm is { IsArray: true, TVProperties.Count: 0 }
            // empty array
            || (cvm.TVProperties.Count == 1 && cvm.TVProperties.FirstOrDefault() is { IsArray: true, TVProperties.Count: 0 })
            //false boolean
            || (cvm.ResolvedData is CBool boolValue && boolValue == false)
            // empty string
            || (cvm.ResolvedData is IRedString str && string.IsNullOrEmpty(str.ToString()))
            // empty raref
            || (cvm.ResolvedData is IRedResourceAsyncReference refAs && refAs.DepotPath == ResourcePath.Empty)
            // empty ref
            || (cvm.ResolvedData is IRedResourceReference refS && refS.DepotPath == ResourcePath.Empty)
            ;
    }
}

public class EditorDifficultyLevelInformation_Easy : EditorDifficultyLevelInformation
{
    public override EditorDifficultyLevel Level => EditorDifficultyLevel.Easy;

    public EditorDifficultyLevelInformation_Easy()
    {
        HiddenFieldNames.AddRange(_informationFields);
        HiddenDataTypes.AddRange(_informationDataTypes);

        HideIfDefaultValueFields.MergeWith(_irrelevantWithDefaultValues);
        HideIfDefaultValueFields.MergeWith(_lowPrioWithDefaultValues);

        HiddenFields.MergeWith(_autoGeneratedFields);
        HiddenFields.MergeWith(_autoGeneratedInfoFields);
        HiddenFields.MergeWith(_genericFields);
    }

    public override bool IsHidden(string fieldName, Type dataType, ChunkViewModel cvm) =>
        base.IsHidden(fieldName, dataType, cvm) || IsReadonly(fieldName, dataType, cvm);
}

public class EditorDifficultyLevelInformation_Default : EditorDifficultyLevelInformation
{
    public override EditorDifficultyLevel Level => EditorDifficultyLevel.Default;

    public EditorDifficultyLevelInformation_Default()
    {
        DisplayAsReadonlyFieldNames.AddRange(_informationFields);
        DisplayAsReadonlyFields.MergeWith(_autoGeneratedInfoFields);

        DisplayAsReadonlyFields.MergeWith(_lowPrioWithDefaultValues);

        ReadonlyFields.MergeWith(_genericFields);
        ReadonlyDataTypes.AddRange(_informationDataTypes);

        HiddenFieldNames.AddRange(_autoGeneratedFieldNames);
        HiddenFields.MergeWith(_autoGeneratedFields);
        HideIfDefaultValueFields.MergeWith(_irrelevantWithDefaultValues);
    }
}

public class EditorDifficultyLevelInformation_Advanced : EditorDifficultyLevelInformation
{
    public override EditorDifficultyLevel Level => EditorDifficultyLevel.Advanced;

    public EditorDifficultyLevelInformation_Advanced()
    {
        DisplayAsReadonlyFieldNames.AddRange(_informationFields);
        DisplayAsReadonlyFields.MergeWith(_autoGeneratedInfoFields);
    }

    public override bool IsHidden(string fieldName, Type dataType, ChunkViewModel cvm) => false;
    public override bool IsReadonly(string fieldName, Type dataType, ChunkViewModel cvm) => false;
}

public static class EditorDifficultyLevelFieldFactory
{
    private static readonly Dictionary<EditorDifficultyLevel, EditorDifficultyLevelInformation> s_fieldInformation = new()
    {
        { EditorDifficultyLevel.None, new EditorDifficultyLevelInformation_Easy() },
        { EditorDifficultyLevel.Easy, new EditorDifficultyLevelInformation_Easy() },
        { EditorDifficultyLevel.Default, new EditorDifficultyLevelInformation_Default() },
        { EditorDifficultyLevel.Advanced, new EditorDifficultyLevelInformation_Advanced() }
    };

    public static EditorDifficultyLevelInformation GetInstance(EditorDifficultyLevel level) => s_fieldInformation[level];
}